package com.aerors.DataProcess;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.FeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.GeometryClipper;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.geometry.jts.WKTReader2;
import org.json.simple.JSONValue;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.io.WKTReader;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.alibaba.fastjson.JSONObject;

import no.ecc.vectortile.VectorTileDecoder;
import no.ecc.vectortile.VectorTileDecoder.Feature;
import no.ecc.vectortile.VectorTileEncoder;

public class TestVectorTile {
	private static GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
	private static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints() );
	private static final String shpPath = "C:\\data\\shapes\\sjy4326.shp";
//	private static final String shpPath = "C:\\data\\shapes\\testSel.shp";
	private static final String outPath = "C:\\data\\vectorTiles\\8\\196\\99.pbf";
	// web墨卡托坐标系椭球(正球)参数
	private static final double originShift = 2 * Math.PI * 6378137 / 2.0;

	private static VectorTileEncoder encoder = new VectorTileEncoder(256,8,false);

	public static void main(String[] args) throws Exception {
//		testGenerateVectorTile(10, 775, 397);
		showResult();
//		calcCoord(10, 773, 410, 19, 6);
	}
	
	public static void calcCoord(int z, int x, int y,int pixelX,int pixelY) {
		double tempLonMin = tile2lon(x, z);
		double tempLonMax = tile2lon(x + 1, z);
		double tempLatMin = tile2lat(y + 1, z);
		double tempLatMax = tile2lat(y, z);
		
		double geoX = PixelXtoGeoX(pixelX, tempLonMin, tempLonMax);
		double geoY = PixelYtoGeoY(pixelY, tempLatMin, tempLatMax);
		
		System.out.println("geoX:" + geoX);
		System.out.println("geoY:" + geoY);
	}
	
	public static void showResult()  throws Exception {
		VectorTileDecoder decoder = new VectorTileDecoder();
		decoder.setAutoScale(false);
		File f = new File(outPath); 
		ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length()); 
		BufferedInputStream in = null; 
		in = new BufferedInputStream(new FileInputStream(f)); 
		int buf_size = 1024; 
		byte[] buffer = new byte[buf_size]; 
		int len = 0; 
		while (-1 != (len = in.read(buffer, 0, buf_size))) { 
		bos.write(buffer, 0, len); 
		} 
		byte[] data = bos.toByteArray();
		
		for (Feature feat : decoder.decode(data)) {
             System.out.println(feat.getLayerName() + ": ");
             System.out.print(feat.getAttributes());
             System.out.println(feat.getGeometry());
        }
	}

	public static void testGenerateVectorTile(int z, int x, int y) throws Exception {
		// 1.打开矢量数据
		File file = new File(shpPath);
		Map<String, Object> map = new HashMap<>();
		map.put("url", file.toURI().toURL());
		DataStore dataStore = DataStoreFinder.getDataStore(map);

		// 2.获取矢量数据元数据
		String typeName = dataStore.getTypeNames()[0];
		FeatureSource<SimpleFeatureType, SimpleFeature> source = dataStore.getFeatureSource(typeName);
		SimpleFeatureType schema = source.getSchema();
		CoordinateReferenceSystem srcCRS = schema.getCoordinateReferenceSystem();
		String geomAttrName = schema.getGeometryDescriptor().getName().toString();

		// 3.获取切片地理范围
		double tempLonMin = tile2lon(x, z);
		double tempLonMax = tile2lon(x + 1, z);
		double tempLatMin = tile2lat(y + 1, z);
		double tempLatMax = tile2lat(y, z);
		ReferencedEnvelope bound = new ReferencedEnvelope(tempLonMin, tempLonMax, tempLatMin, tempLatMax, srcCRS);
		System.out.println(bound);

		Coordinate[] coords = new Coordinate[] { new Coordinate(bound.getMinX(), bound.getMaxY()),
				new Coordinate(bound.getMaxX(), bound.getMaxY()), new Coordinate(bound.getMaxX(), bound.getMinY()),
				new Coordinate(bound.getMinX(), bound.getMinY()), new Coordinate(bound.getMinX(), bound.getMaxY()) };

		// 4.获取切片范围内的要素
		Filter filter = ff.bbox(ff.property(geomAttrName), bound);
		FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures(filter);

		GeometryClipper gc = new GeometryClipper(bound);

		// 5.循环要素,将每个要素的地理坐标转换为像素坐标
		FeatureIterator<SimpleFeature> iterator = collection.features();
		while (iterator.hasNext()) {
			SimpleFeature feature = iterator.next();
			String featureId = feature.getID();
			long id = Long.parseLong(featureId.substring(featureId.indexOf(".") + 1, featureId.length()));
			Geometry geom = null;
			Map<String, Object> attrMap = new HashMap<>();
			for (Property p : feature.getProperties()) {
				if (p.getName().toString().equals(geomAttrName)) {
					geom = (Geometry) p.getValue();

					System.out.println(geom.toText());
//					geom = gc.clip(geom, false);

//					System.out.println("raw geom :" + geom.toText());
					Coordinate[] cs = geom.getCoordinates();
					System.out.println("raw size :" + cs.length);
					for (Coordinate c : cs) {
						c.x = geoXtoPixelX(c.x, tempLonMin, tempLonMax);
						c.y = geoYtoPixelY(c.y, tempLatMin, tempLatMax);
					}
				} else {
					attrMap.put(p.getName().toString(), p.getValue());
				}
			}
//			System.out.println(featureId);
			geom = filterGeometry(geom);
			encoder.addFeature(typeName, attrMap, geom, id);
		}

		// 6.按照MapBox Vector Tile规范进行二进制编码
		byte[] vtCode = encoder.encode();

		// 7.按照TMS规范保存到本地文件
		FileOutputStream fous = new FileOutputStream(new File(outPath));
		fous.write(vtCode);
		fous.flush();
		fous.close();
	}
	
	/**
	 * 过滤<b>MultiPolygon</b>和<b>MultiLineString</b><br>
	 * 去除<b>MultiPolygon</b>面积过小的部分和<b>MultiLineString</b>中长度过小的部分
	 * @param geom
	 * @return
	 */
	public static Geometry filterGeometry(Geometry geom) {
		String geomType = geom.getGeometryType();
		Geometry newGeometry = null;
		switch (geomType) {
		//处理MultiPolygon
		case "MultiPolygon":
			MultiPolygon mp = (MultiPolygon) geom;
			List<Polygon> polygonList = new ArrayList<Polygon>();
			
			for (int i = 0; i < mp.getNumGeometries(); i++) {
				Polygon p = (Polygon) mp.getGeometryN(i);
				if (p.getArea() > 1.0d) {
					polygonList.add(p);
				}
			}
			
			Polygon[] polyArray = new Polygon[polygonList.size()];
			polygonList.toArray(polyArray);
			newGeometry = new MultiPolygon(polyArray, geometryFactory);
			break;
		//处理MultiLineString
		case "MultiLineString":
			MultiLineString ml = (MultiLineString) geom;
			List<LineString> lineList = new ArrayList<LineString>();
			
			for (int i = 0; i < ml.getNumGeometries(); i++) {
				LineString l = (LineString) ml.getGeometryN(i);
				if (l.getLength() > 1.0d) {
					lineList.add(l);
				}
			}
			
			LineString[] lines = new LineString[lineList.size()];
			lineList.toArray(lines);
			newGeometry = new MultiLineString(lines, geometryFactory);
			break;
		//其他几何类型不处理
		default:
			newGeometry = geom;
			break;
		}

		return newGeometry;
	}

	public static void testSigleGeometryVT(int x, int y, int z) throws Exception {
		// 1.根据xyz求切片范围
		double lngLeft = tile2lon(x, z);
		double latUp = tile2lat(y, z);
		double lngRight = tile2lon(x + 1, z);
		double latDown = tile2lat(y + 1, z);

		System.out.println("lngLeft:" + lngLeft + ";lngRight:" + lngRight + ";latUp:" + latUp + ";latDown:" + latDown);

		// 2.根据切片范围计算几何要素在切片内的像素坐标
		GeometryJSON transform = new GeometryJSON(6);

		WKTReader reader = new WKTReader();
		Geometry geom = reader.read(
				"POLYGON ((101.25 48.92249926375824, 112.5 48.92249926375824, 112.5 40.97989806962013, 101.25 40.97989806962013, 101.25 48.92249926375824))");

		JSONObject rawGeoJson = JSONObject.parseObject(transform.toString(geom));
		System.out.println(rawGeoJson);
		Coordinate[] cs = geom.getCoordinates();
		for (Coordinate c : cs) {
			c.x = geoXtoPixelX(c.x, lngLeft, lngRight);
			c.y = geoYtoPixelY(c.y, latDown, latUp);
		}
		JSONObject newGeoJson = JSONObject.parseObject(transform.toString(geom));
		System.out.println(newGeoJson);

		// 3.对几何要素和属性信息按照Vector Tile规范进行二进制编码
		Map<String, Object> attrs = new HashMap<>();
		attrs.put("pid", "1");
		attrs.put("name", "111");
		attrs.put("area", 999l);
		attrs.put("code", 6101);

		encoder.addFeature("road", attrs, geom);
		byte[] encoded = encoder.encode();

		// 4.存储到本地文件
		FileOutputStream fous = new FileOutputStream(new File(outPath));
		fous.write(encoded);
		fous.flush();
		fous.close();

		// 5.解码测试
		VectorTileDecoder decoder = new VectorTileDecoder();
		decoder.setAutoScale(false);
		byte[] data = null;
		FileInputStream fis = new FileInputStream(outPath);
		ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
		byte[] b = new byte[1000];
		int n;
		while ((n = fis.read(b)) != -1) {
			bos.write(b, 0, n);
		}
		fis.close();
		data = bos.toByteArray();
		bos.close();
		List<Feature> list = decoder.decode(data).asList();
		Geometry decodeGeom = list.get(0).getGeometry();
		System.out.println(decodeGeom.toText());
		Map<String, Object> decodedAttributes = list.get(0).getAttributes();
		System.out.println(decodedAttributes);
	}

	public static JSONObject GeomWktToGeoJson(String wkt) throws Exception {
		WKTReader2 wktReader = new WKTReader2();
		Geometry g = (Geometry) wktReader.read(wkt);
		GeometryJSON j = new GeometryJSON(20);// 注意在转换的过程中使用默认的构造参数转换的点会出现丢失的现象，这里传入的参数可以使传入点精度精确到20位
		Object obj = JSONValue.parse(j.toString(g));
		JSONObject array = (JSONObject) obj;
		// System.out.println(array.toJSONString());
		return array;
	}

	public static double PixelXtoGeoX(int pixelX, double xmin, double xmax) {
		Double result = (((xmax - xmin) / 255) * pixelX) + xmin;
		return result;
	}

	public static double PixelYtoGeoY(int pixelY, double ymin, double ymax) {
		Double result = (((ymax - ymin) / 255) * pixelY) + ymin;
		return result;
	}

	public static int geoXtoPixelX(double x, double xmin, double xmax) {
		Double result = ((x - xmin) / ((xmax - xmin) / 255));
		return (int) Math.ceil(result);
	}

	public static int geoYtoPixelY(double y, double ymin, double ymax) {
		Double result = ((y - ymin) / ((ymax - ymin) / 255));
		return (int) Math.ceil(result);
	}

	public static String parseXyz2Bound(int x, int y, int z) {

		StringBuilder sb = new StringBuilder("POLYGON ((");

		double lngLeft = tile2lon(x, z);

		double latUp = tile2lat(y, z);

		double lngRight = tile2lon(x + 1, z);

		double latDown = tile2lat(y + 1, z);

		sb.append(lngLeft + " " + latUp + ", ");

		sb.append(lngRight + " " + latUp + ", ");

		sb.append(lngRight + " " + latDown + ", ");

		sb.append(lngLeft + " " + latDown + ", ");

		sb.append(lngLeft + " " + latUp + ")) ");

		return sb.toString();

	}

	/**
	 * 4326坐标系经纬度转3857坐标系的XY
	 * 
	 * @param lon
	 * @param lat
	 * @return
	 */
	public static double[] lonLat2WebMercator(double lon, double lat) {
		double[] mercator = new double[2];
		double x = lon * originShift / 180.0;
		double y = Math.log(Math.tan((90 + lat) * Math.PI / 360.0)) / (Math.PI / 180.0);
		y = y * originShift / 180;

		mercator[0] = x;
		mercator[1] = y;

		return mercator;
	}

	/**
	 * 3857坐标系的XY转4326坐标系经纬度
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public static double[] WebMercator2lonLat(double x, double y) {
		double[] lonlat = new double[2];
		double lon = (x / originShift) * 180;
		double lat = (y / originShift) * 180;
		lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0);

		lonlat[0] = lon;
		lonlat[1] = lat;

		return lonlat;
	}

	/**
	 * OSM切片规则下 已知切片列号和缩放级别,求经度
	 * 
	 * @param x
	 * @param z
	 * @return
	 */
	public static double tile2lon(int x, int z) {
		return x / Math.pow(2.0, z) * 360.0 - 180;
	}

	/**
	 * OSM切片规则下 已知切片行号和缩放级别,求纬度
	 * 
	 * @param y
	 * @param z
	 * @return
	 */
	public static double tile2lat(int y, int z) {
		double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z);
		return Math.toDegrees(Math.atan(Math.sinh(n)));
	}
}
